﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;

using SharpGL.SceneGraph;

namespace SharpGL
{
    /// <summary>
    /// The SceneControl is an OpenGL control that contains and draws a Scene object.
    /// </summary>
    [System.Drawing.ToolboxBitmap(typeof(SceneControl), "SharpGL.png")]
    public partial class SceneControl : OpenGLControl, INotifySceneTreeChanged
    {
        #region Private Fields

        /// <summary>
        /// This is the scene itself.
        /// </summary>
        private Scene scene = new Scene();

        /// <summary>
        /// Font used for render the draw time.
        /// </summary>
        private Font drawFPSFont = new Font("Courier New", 12.0f);

        /// <summary>
        /// A stopwatch used for timing rendering.
        /// </summary>
        private Stopwatch stopwatch = new Stopwatch();

        #endregion Private Fields

        #region Private Methods

        private void OnScenePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Invalidate();
        }

        /// <summary>
        /// Called when scene tree changed.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">A SceneTreeChangeEventArgs that contains the event data.</param>
        private void OnSceneTreeChanged(object sender, SceneTreeChangeEventArgs e)
        {
            Invalidate();

            NotifySceneTreeChanged(sender, e);
        }

        #endregion Private Methods

        #region Protected Methods

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (components != null)
                    components.Dispose();

                if (drawFPSFont != null)
                    drawFPSFont.Dispose();
            }

            base.Dispose(disposing);
        }

        /// <summary>
        /// Call this function to do the SceneTreeChanged event.
        /// </summary>
        protected void NotifySceneTreeChanged(object sender, SceneTreeChangeEventArgs e)
        {
            var handler = SceneTreeChanged;
            if (handler != null)
                handler(sender, e);
        }

        protected override void OnOpenGLContextInitialized()
        {
            base.OnOpenGLContextInitialized();

            if (scene != null)
                scene.CreateInContext(CurrentOpenGLContext);
        }

        protected override void OnOpenGLContextReleasing()
        {
            if (scene != null)
                scene.DestroyInContext();

            base.OnOpenGLContextReleasing();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            //  Start the stopwatch so that we can time the rendering.
            stopwatch.Restart();

            if (OpenGL.IsValidContext(CurrentOpenGLContext))
            {
                //	Make sure it's our instance of openSharpGL that's active.
                CurrentOpenGLContext.MakeCurrent();

                //	Do the scene drawing.
                scene.Render();

                //	If there is a draw handler, then call it.
                DoOpenGLDraw(e);

                //  Draw the FPS.
                if (DrawFPS)
                {
                    CurrentOpenGLContext.DrawText(5, 5, drawFPSFont, 1.0f, 0.0f, 0.0f,
                        string.Format("Draw Time: {0:0.0000} ms ~ {1:0.0} FPS", FrameTime, 1000.0 / FrameTime));
                    CurrentOpenGLContext.Flush();
                }

                //	Blit our offscreen bitmap.
                IntPtr handleDeviceContext = IntPtr.Zero;
                try
                {
                    handleDeviceContext = e.Graphics.GetHdc();
                    CurrentOpenGLContext.Blit(handleDeviceContext);
                }
                finally
                {
                    if (handleDeviceContext != IntPtr.Zero)
                        e.Graphics.ReleaseHdc(handleDeviceContext);
                }

                //	If's there's a GDI draw handler, then call it.
                if (CurrentOpenGLContext.RenderContextProvider.GDIDrawingEnabled != false)
                    DoGDIDraw(e);
            }

            //  Stop the stopwatch.
            stopwatch.Stop();

            //  Store the frame time.
            FrameTime = stopwatch.Elapsed.TotalMilliseconds;
        }

        /// <summary>
        /// Process the <see cref="E:PaintBackground"/> event.
        /// </summary>
        /// <param name="e">The <see cref="System.Windows.Forms.PaintEventArgs"/> instance containing the event data.</param>
        protected override void OnPaintBackground(PaintEventArgs e)
        {
            //	We override this, and don't call the base, i.e we don't paint
            //	the background.
        }

        /// <summary>
        /// Process the <see cref="E:System.Windows.Forms.Control.SizeChanged"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> that contains the event data.</param>
        protected override void OnSizeChanged(EventArgs e)
        {
            //  Don't call the base- we handle sizing ourselves.

            //  If we do not have a render context there is nothing more
            //  we can do.
            if (OpenGL.IsValidContext(CurrentOpenGLContext))
            {
                //	CurrentOpenGLContext needs to resize the viewport.
                CurrentOpenGLContext.SetDimensions(Width, Height);
                scene.Resize(Width, Height);

                Invalidate();
            }
        }

        #endregion Protected Methods

        #region Constructors

        public SceneControl()
        {
            // This call is required by the Windows.Forms Form Designer.
            InitializeComponent();

            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.UserPaint, true);

            scene.PropertyChanged += new PropertyChangedEventHandler(OnScenePropertyChanged);
            scene.SceneTreeChanged += new SceneTreeChangeEventHandler(OnSceneTreeChanged);
        }

        #endregion Constructors

        #region Public Events

        [Description("Called when scene tree has been changed."), Category("SharpGL")]
        public event SceneTreeChangeEventHandler SceneTreeChanged;

        #endregion Public Events

        #region Public Properties

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public virtual Scene Scene
        {
            get { return scene; }
            set
            {
                if (scene != value)
                {
                    if (OpenGL.IsValidContext(scene.CurrentOpenGLContext))
                        scene.DestroyInContext();

                    scene.PropertyChanged -= new PropertyChangedEventHandler(OnScenePropertyChanged);
                    scene.SceneTreeChanged -= new SceneTreeChangeEventHandler(OnSceneTreeChanged);

                    if (value != null)
                        scene = value;
                    else
                        scene = new Scene();

                    scene.PropertyChanged += new PropertyChangedEventHandler(OnScenePropertyChanged);
                    scene.SceneTreeChanged += new SceneTreeChangeEventHandler(OnSceneTreeChanged);

                    if (OpenGL.IsValidContext(CurrentOpenGLContext))
                    {
                        scene.CreateInContext(CurrentOpenGLContext);
                        scene.Resize(Width, Height);
                    }

                    Invalidate();
                }
            }
        }

        #endregion Public Properties
    }
}